package io.cici.cc.mybatis.lite.executor.resultset;

import io.cici.cc.mybatis.lite.executor.ExecutorException;
import io.cici.cc.mybatis.lite.mapping.MappedStatement;
import io.cici.cc.mybatis.lite.mapping.ResultMapping;
import io.cici.cc.mybatis.lite.mapping.ResultMappings;
import io.cici.cc.mybatis.lite.reflection.ClassMetaData;
import io.cici.cc.mybatis.lite.reflection.MetaDataObject;
import io.cici.cc.mybatis.lite.reflection.ReflectorFactory;
import io.cici.cc.mybatis.lite.reflection.factory.ObjectFactory;
import io.cici.cc.mybatis.lite.session.Configuration;
import io.cici.cc.mybatis.lite.type.TypeHandler;
import io.cici.cc.mybatis.lite.type.TypeHandlerRegistry;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

public class ResultSetHandlerImpl implements io.cici.cc.mybatis.lite.executor.resultset.ResultSetHandler {


    private final Configuration configuration;
    private final MappedStatement mappedStatement;

    private final ResultContextHandler<?> resultContextHandler;
    private final TypeHandlerRegistry typeHandlerRegistry;
    private final ObjectFactory objectFactory;
    private final ReflectorFactory reflectorFactory;


    public ResultSetHandlerImpl(MappedStatement mappedStatement,
                                ResultContextHandler<?> resultContextHandler) {
        this.configuration = mappedStatement.getConfiguration();
        this.mappedStatement = mappedStatement;

        this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        this.objectFactory = Configuration.objectFactory;
        this.reflectorFactory = Configuration.reflectorFactory;
        this.resultContextHandler = resultContextHandler;
    }

    @Override
    public List<Object> handleResultSets(Statement statement) throws SQLException {
        ResultSet resultSet = statement.getResultSet();
        ResultSetWrapper resultSetWrapper = new ResultSetWrapper(resultSet, configuration);
        ResultMappings resultMappings = mappedStatement.getResultMappings();
        final List<Object> list = new ArrayList<>();
        handleResultSet(resultSetWrapper, resultMappings, list);
        return list;
    }

    private void closeResultSet(ResultSet resultSet) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    private void handleResultSet(ResultSetWrapper resultSetWrapper, ResultMappings resultMappings, List<Object> multipleResults)
            throws SQLException {
        try {
            if (resultContextHandler == null) {
                ResultContextHandlerImpl defaultResultHandler = new ResultContextHandlerImpl(objectFactory);
                handleRowValues(resultSetWrapper, resultMappings, defaultResultHandler);
                multipleResults.add(defaultResultHandler.getResultList());
            } else {
                handleRowValues(resultSetWrapper, resultMappings, resultContextHandler);
            }
        } finally {

            closeResultSet(resultSetWrapper.getResultSet());
        }
    }

    public void handleRowValues(ResultSetWrapper resultSetWrapper, ResultMappings resultMappings,
                                ResultContextHandler<?> resultContextHandler) throws SQLException {
        ResultContextImpl<Object> resultSetSetContext = new ResultContextImpl<>();
        ResultSet resultSet = resultSetWrapper.getResultSet();
        while (!resultSet.isClosed() && resultSet.next()) {
            Object rowValue = getRowValue(resultSetWrapper, resultMappings);
            storeObject(resultContextHandler, resultSetSetContext, rowValue);
        }
    }

    private void storeObject(ResultContextHandler<?> resultContextHandler,
                             ResultContextImpl<Object> resultSetSetContext, Object rowValue) {
        resultSetSetContext.nextResultObject(rowValue);
        ((ResultContextHandler<Object>) resultContextHandler).handle(resultSetSetContext);
    }

    private Object getRowValue(ResultSetWrapper resultSetWrapper, ResultMappings resultMappings)
            throws SQLException {

        Object rowValue = createResultObject(resultSetWrapper, resultMappings);
        MetaDataObject metaDataObject = MetaDataObject.forObject(rowValue, null, null, null);
        applyPropertyMappings(resultSetWrapper, resultMappings, metaDataObject);
        return rowValue;
    }


    private boolean applyPropertyMappings(ResultSetWrapper resultSetWrapper, ResultMappings resultMappings, MetaDataObject metaDataObject) throws SQLException {

        boolean foundValues = false;
        final List<ResultMapping> resultMappingList = resultMappings.getResultMappingList();
        for (ResultMapping resultMapping : resultMappingList) {
            String column = resultMapping.getColumn();

            if (column != null) {
                Object value = getPropertyMappingValue(resultSetWrapper.getResultSet(), resultMapping);

                final String property = resultMapping.getProperty();
                if (property == null) {
                    continue;
                }

                if (value != null) {
                    foundValues = true;
                }
                if (value != null || configuration.isCallSettersOnNulls()
                        && !metaDataObject.getSetterType(property).isPrimitive()) {
                    // gcode issue #377, call setter on nulls (value is not 'found')
                    metaDataObject.setValue(property, value);
                }
            }
        }
        return foundValues;
    }

    private Object getPropertyMappingValue(ResultSet resultSet, ResultMapping propertyMapping) throws SQLException {
        final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
        final String column = propertyMapping.getColumn();
        return typeHandler.get(resultSet, column);
    }


    private Object createResultObject(ResultSetWrapper rsw, ResultMappings resultMappings) throws SQLException {
        final Class<?> resultType = resultMappings.getType();
        final ClassMetaData metaType = ClassMetaData.build(reflectorFactory, resultType);

        if (hasTypeHandlerForResultObject(rsw, resultType)) {
            return createPrimitiveResultObject(rsw, resultMappings);
        }
        if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
            return objectFactory.create(resultType);
        }
        throw new ExecutorException("Do not know how to create an instance of " + resultType);
    }

    private boolean hasTypeHandlerForResultObject(ResultSetWrapper rsw, Class<?> resultType) {
        return true;
    }


    private Object createPrimitiveResultObject(ResultSetWrapper resultSetWrapper, ResultMappings resultMappings)
            throws SQLException {
        final Class<?> resultType = resultMappings.getType();
        final String columnName;
        if (!resultMappings.getResultMappingList().isEmpty()) {
            final List<ResultMapping> resultMappingList = resultMappings.getResultMappingList();
            final ResultMapping resultMapping = resultMappingList.get(0);
            columnName = resultMapping.getColumn();
        } else {
            columnName = resultSetWrapper.getColumnNames().get(0);
        }
        final TypeHandler<?> typeHandler = resultSetWrapper.getTypeHandler(resultType, columnName);
        return typeHandler.get(resultSetWrapper.getResultSet(), columnName);
    }


}
